# ============================================================================
# 文件路径: src/cron.nim
# 文件用途: 定时任务模块 - 负责定期更新 Nim 包数据和仓库信息
# 
# 主要功能:
# 1. 每 12 小时从官方源更新包列表数据
# 2. 每周更新包的仓库信息 (GitHub/GitLab/Codeberg)
# 3. 将包数据存储到 Redis 缓存中以提高查询性能
# 4. 支持开发模式和发布模式的不同数据获取策略
# ============================================================================

# 导入标准库模块
import std/[
  httpclient,    # HTTP 客户端，用于下载包数据
  os,           # 操作系统接口，用于文件操作
  json,         # JSON 解析，处理包数据格式
  options,      # 可选类型，处理可能为空的值
  strutils,     # 字符串工具，用于字符串处理
  times         # 时间处理，用于时间戳和间隔计算
]

# 导入第三方库
import ready    # Redis 客户端库，用于缓存操作

# 导入项目内部模块
import ./cache            # 缓存连接池管理
import ./package_fetching # 包仓库信息获取功能

# ============================================================================
# 函数名: updatePackages
# 功能: 定期更新 Nim 包列表数据 (每 12 小时执行一次)
# 线程安全: 使用 {.thread.} 标记，可在独立线程中运行
# ============================================================================
proc updatePackages*() {.thread.} =
  var packages: JsonNode  # 存储从官方源获取的包数据
  
  # ====================================================================
  # 根据编译模式选择数据获取策略
  # ====================================================================
  
  # 发布模式：直接从 GitHub 官方仓库下载最新数据
  when defined(release):
    echo "🚀 发布模式：正在从官方源下载 packages.json..."
    try:
      let client = newHttpClient()
      client.timeout = 30000  # 发布模式设置 30 秒超时
      let response = client.get("https://raw.githubusercontent.com/nim-lang/packages/refs/heads/master/packages.json")
      packages = response.body.parseJson()
      echo "✅ 成功获取包数量: ", packages.len
      client.close()
    except CatchableError as e:
      echo "❌ 发布模式网络连接失败: ", e.msg
      echo "💡 提示: 请检查网络连接或稍后重试"
      return

  # 开发模式：优先使用本地缓存，减少网络请求
  elif defined(dev):
    if fileExists("packages.json"):
      echo "🧪 开发模式：使用本地缓存的 packages.json 文件"
      packages = readFile("packages.json").parseJson()
      echo "📁 从缓存加载了 ", packages.len, " 个包"
    else:
      echo "🧪 开发模式：本地缓存不存在，正在下载 packages.json..."
      try:
        let client = newHttpClient()
        client.timeout = 10000  # 开发模式设置 10 秒超时
        let response = client.get("https://raw.githubusercontent.com/nim-lang/packages/refs/heads/master/packages.json")
        packages = response.body.parseJson()
        # 保存到本地以供下次使用
        writeFile("packages.json", $packages)
        echo "💾 已保存到本地缓存，包数量: ", packages.len
        client.close()
      except CatchableError as e:
        echo "❌ 开发模式网络连接失败: ", e.msg
        echo "💡 提示: 开发模式可以离线工作，请手动下载 packages.json"
        echo "🔧 解决方案: 手动下载文件到项目根目录"
        echo "   下载地址: https://raw.githubusercontent.com/nim-lang/packages/refs/heads/master/packages.json"
        return
  
  # 默认模式 (未定义任何模式时)：使用开发模式的逻辑
  else:
    echo "⚠️  未检测到编译模式，使用默认开发模式行为"
    if fileExists("packages.json"):
      echo "📁 使用本地缓存的 packages.json 文件"
      packages = readFile("packages.json").parseJson()
      echo "✅ 从缓存加载了 ", packages.len, " 个包"
    else:
      echo "🌐 本地缓存不存在，正在下载 packages.json..."
      try:
        let client = newHttpClient()
        client.timeout = 10000  # 设置 10 秒超时
        let response = client.get("https://raw.githubusercontent.com/nim-lang/packages/refs/heads/master/packages.json")
        packages = response.body.parseJson()
        # 保存到本地以供下次使用
        writeFile("packages.json", $packages)
        echo "💾 已保存到本地缓存，包数量: ", packages.len
        client.close()
      except CatchableError as e:
        echo "❌ 网络连接失败: ", e.msg
        echo "💡 提示: 请检查网络连接或使用离线模式"
        echo "🔧 解决方案: 手动下载 packages.json 文件到项目根目录"
        echo "   下载地址: https://raw.githubusercontent.com/nim-lang/packages/refs/heads/master/packages.json"
        return

  # 数据验证：确保获取到有效的包数组
  if packages.kind != JArray or packages.len == 0:
    echo "错误：未找到有效的包数据"
    return

  # ========================================================================
  # Redis 数据存储阶段：将包数据结构化存储到 Redis 中
  # 使用哈希结构存储每个包的详细信息，便于快速查询和更新
  # ========================================================================
  try:
    cachePool.withConnection conn:
      # 清理旧数据：删除现有的包名集合，准备存储新数据
      discard conn.command("DEL", "package_names")
      echo "已清理旧的包数据，开始存储新数据..."

      # 遍历每个包并存储到 Redis
      for package in packages:
        # 安全获取包名，处理可能缺失的字段
        let name = if package.hasKey("name"): package["name"].getStr() else: ""
        if name.len == 0:
          echo "跳过无名称的包"
          continue

        # 存储包的基本信息到 Redis 哈希结构
        # 键格式: "package:包名" -> 哈希表存储所有字段
        # 使用多个 HSET 命令来兼容不同版本的 Redis
        let packageKey = "package:" & name
        discard conn.command("HSET", packageKey, "name", name)
        discard conn.command("HSET", packageKey, "url", if package.hasKey("url"): package["url"].getStr() else: "")
        discard conn.command("HSET", packageKey, "method", if package.hasKey("method"): package["method"].getStr() else: "")
        discard conn.command("HSET", packageKey, "description", if package.hasKey("description"): package["description"].getStr() else: "")
        discard conn.command("HSET", packageKey, "license", if package.hasKey("license"): package["license"].getStr() else: "")
        discard conn.command("HSET", packageKey, "web", if package.hasKey("web"): package["web"].getStr() else: "")

        # 处理包标签：使用 Redis 集合存储，支持快速标签查询
        let tags = if package.hasKey("tags"): package["tags"].getElems() else: @[]
        for tag in tags:
          # 为包添加标签：package:包名:tags -> 集合存储该包的所有标签
          discard conn.command("SADD", "package:" & name & ":tags", tag.getStr())
          # 为标签添加包：tag:标签名 -> 集合存储拥有该标签的所有包
          discard conn.command("SADD", "tag:" & tag.getStr(), name)

        # 将包名添加到全局包名集合，用于获取所有包列表
        discard conn.command("SADD", "package_names", name)

      # 存储系统元数据，用于统计和状态查询
      discard conn.command("SET", "packages_count", $packages.len)  # 总包数量
      discard conn.command("SET", "last_updated", $getTime().toUnix())  # 最后更新时间戳
      echo "📊 已更新系统元数据：包总数 ", packages.len, "，更新时间 ", getTime().toUnix()

    echo "✅ 包数据更新完成！共处理 ", packages.len, " 个包"
  except CatchableError as e:
    echo "❌ Redis 连接失败: ", e.msg
    echo "💡 提示: 请确保 Redis 服务器正在运行"
    echo "🔧 解决方案:"
    echo "   1. 安装并启动 Redis 服务器"
    echo "   2. 或者使用 Docker: docker run -d -p 6379:6379 redis:alpine"
    echo "   3. 检查 Redis 连接配置"
    return
  
  echo "😴 休眠 12 小时后进行下次更新..."
  sleep(43200 * 1000)  # 43200 秒 = 12 小时


# ============================================================================
# 函数名: updatePackagesWithRepoInfo
# 功能: 每周更新包的仓库信息 (GitHub/GitLab/Codeberg)
# 
# 详细说明:
# - 获取包的最新版本、更新时间等仓库元数据
# - 支持 GitHub、GitLab、Codeberg 三大代码托管平台
# - 使用智能调度，避免重复更新和 API 限制
# - 实现渐进式更新，减少对外部 API 的压力
# ============================================================================
proc updatePackagesWithRepoInfo*() {.thread.} =
  ## 每周更新包并丰富仓库信息 (GitHub, GitLab, Codeberg)
  ## 这个函数会定期获取每个包的最新仓库信息，包括版本、更新时间等
  # 配置常量
  const WEEKLY_INTERVAL_SECONDS = 7 * 24 * 60 * 60  # 7 天的秒数 (604800 秒)
  const REDIS_KEY_LAST_REPO_UPDATE = "last_repo_update_timestamp"  # Redis 中存储上次更新时间的键

  # 主循环：持续监控并在适当时机执行更新
  while true:
    # ====================================================================
    # 智能调度检查：判断是否需要执行仓库信息更新
    # ====================================================================
    var shouldRun = false
    try:
      cachePool.withConnection conn:
        # 从 Redis 获取上次更新的时间戳
        let lastUpdate = conn.command("GET", REDIS_KEY_LAST_REPO_UPDATE)
        let lastUpdateOpt = lastUpdate.to(Option[int])

        if lastUpdateOpt.isNone:
          # 首次运行：没有更新记录，需要立即更新
          echo "🆕 检测到首次运行，准备进行仓库信息更新..."
          shouldRun = true
        else:
          # 计算距离上次更新的时间间隔
          let timeSinceLastUpdate = getTime().toUnix() - lastUpdateOpt.get
          shouldRun = timeSinceLastUpdate >= WEEKLY_INTERVAL_SECONDS
          
          if shouldRun:
            echo "⏰ 距离上次更新已超过 7 天，准备进行仓库信息更新..."
          else:
            let remainingTime = WEEKLY_INTERVAL_SECONDS - timeSinceLastUpdate
            echo "⏳ 距离下次更新还有 ", remainingTime div 3600, " 小时"
    except CatchableError as e:
      echo "❌ Redis 连接失败，跳过仓库信息更新: ", e.msg
      echo "🔄 将在 1 小时后重试..."

    # 如果不需要更新，休眠后继续检查
    if not shouldRun:
      echo "😴 仓库信息更新尚未到期，休眠 1 小时后再次检查..."
      sleep(60 * 60 * 1000)  # 休眠 1 小时 (3600 秒)
      continue

    # ====================================================================
    # 开始仓库信息更新流程
    # ====================================================================
    echo "🚀 开始每周仓库信息更新任务..."
    let updateStartTime = getTime().toUnix()

    # 创建 HTTP 客户端，用于调用各平台 API
    # 使用单个客户端实例可以复用连接，提高性能
    let client = newHttpClient()
    client.headers = newHttpHeaders({
      "User-Agent": "nimpackages/1.0 (风铃草包管理器)"  # 设置友好的 User-Agent
    })

    # 统计变量
    var processedCount = 0  # 已处理的包数量
    var enrichedCount = 0   # 成功获取仓库信息的包数量
    
    try:
      # ================================================================
      # 批量处理所有包的仓库信息
      # ================================================================
      cachePool.withConnection conn:
        # 获取所有包名列表
        let packageNames = conn.command("SMEMBERS", "package_names")
        let names = packageNames.to(seq[string])
        echo "📦 准备处理 ", names.len, " 个包的仓库信息"

        # 逐个处理每个包
        for name in names:
          # 从 Redis 获取包的详细信息
          let hash = conn.command("HGETALL", "package:" & name)
          let hashArray = hash.to(seq[Option[string]])

          if hashArray.len > 0:
            var url = ""
            # 解析哈希数组，提取仓库 URL
            # Redis HGETALL 返回 [key1, value1, key2, value2, ...] 格式
            for i in countup(0, hashArray.len - 2, 2):
              let key = hashArray[i].get("")
              let value = hashArray[i + 1]
              if key == "url" and value.isSome:
                url = value.get
                break

            # 检查是否为支持的代码托管平台
            let (_, _, platform) = extractRepoInfo(url)
            if platform.len > 0:
              echo "🔍 正在获取 ", name, " 的仓库信息 (", platform, ")..."
              
              # 调用仓库信息获取函数
              let info = fetchPackageRepoInfo(name, url, client)
              if info.isSome:
                # 将获取到的仓库信息存储到 Redis
                discard conn.command("HSET", "package:" & name,
                  "latest_version", info.get["latest_version"].getStr(),      # 最新版本号
                  "latest_update_date", info.get["latest_update_date"].getStr(), # 最后更新日期
                  "repo_last_checked", info.get["last_checked"].getStr(),     # 最后检查时间
                  "repo_platform", info.get["platform"].getStr()             # 托管平台
                )
                enrichedCount.inc()
                echo "✅ 成功更新 ", name, " 的仓库信息"
              else:
                echo "⚠️  无法获取 ", name, " 的仓库信息"

              # API 速率限制：请求间隔 1 秒，避免触发平台限制
              sleep(1000)
            else:
              echo "⏭️  跳过 ", name, " (不支持的平台或无效 URL)"

            processedCount.inc()

            # 开发模式和默认模式：限制处理数量，避免开发时等待过久
            when defined(dev) or not defined(release):
              echo "🧪 开发模式：已处理 ", processedCount, " 个包，丰富了 ", enrichedCount, " 个包的信息"
              if processedCount > 5:  # 开发模式只处理前 5 个包
                echo "🛑 开发模式限制：停止处理更多包"
                break

            # 进度报告：每处理 100 个包输出一次进度
            if processedCount mod 100 == 0:
              echo "📊 进度报告：已处理 ", processedCount, " 个包，成功丰富 ", enrichedCount, " 个包的仓库信息"

      # ================================================================
      # 更新完成：记录时间戳和统计信息
      # ================================================================
      cachePool.withConnection conn:
        discard conn.command("SET", REDIS_KEY_LAST_REPO_UPDATE, $updateStartTime)
        echo "📝 仓库信息更新完成时间戳: ", updateStartTime

      echo "🎉 仓库信息更新任务完成！"
      echo "📊 统计信息："
      echo "   - 总处理包数: ", processedCount
      echo "   - 成功丰富包数: ", enrichedCount
      echo "   - 成功率: ", if processedCount > 0: (enrichedCount * 100 div processedCount) else: 0, "%"

    finally:
      # 确保 HTTP 客户端被正确关闭，释放资源
      client.close()
      echo "🔒 HTTP 客户端已关闭"

    echo "✅ 仓库信息更新流程完成，下次更新将在 7 天后进行"
    echo "😴 休眠 1 小时后再次检查调度..."
    # 休眠 1 小时后重新进入循环检查
    sleep(60 * 60 * 1000)  # 3600 秒 = 1 小时


